Class 8 - Platformer Part 2: Animation & Tilemap
在上一节课中,我们搭建了平台跳跃游戏的基础,包括玩家移动 、瞄准和坠落重置。本节课我们将深入优化这些系统,解决潜在的 Bug,并引入 Tilemap(瓦片地图)系统和高级动画状态机。
Class 8 Overview
- 回顾与优化:OnBecameInvisible vs Trigger
- 优化瞄准系统:准心跟随鼠标位置
- 实现子弹发射与销毁
- 实现二段跳与射线检测
- 引入 Tilemap 瓦片地图系统
- 构建角色动画状态机
1. 回顾与优化:OnBecameInvisible vs Trigger
上节课课上,在构建“位于场景底部的触发器(Trigger)来检测玩家是否掉落”的时候,我们对于“如何实现?”这个问题进行了简单的提问。
本节课录屏开头我们进行了补充说明:为什么不使用 OnBecameInvisible() 函数(当物体离开相机视野时触发)?
OnBecameInvisible() 看似可行,但它并非实时触发。为了运行的可靠性,它会有 ~0.5 秒的延迟。在游戏中,这会导致玩家掉出屏幕后,需要等待一小段时间才会重置场景,体验不佳。
因此,我们使用触发器(OnTriggerEnter2D)的“笨方法”是更优解,因为它能提供实时的碰撞检测。
2. 优化瞄准系统:准心跟随鼠标位置
上节课我们实现了准心绕角色旋转的效果:
- Player 物体包含一个空的子物体 AimPivot
- AimPivot 包含子物体 Reticle(准心)
- 通过旋转 AimPivot,实现 Reticle 的"公转"效果
目前的问题是:准心只能在固定半径的圆形范围内旋转。
2.1 更新准心位置
于是这节课我们将其优化为准心直接跟随鼠标位置。
public Transform reticle; // 准心对象引用
void Update()
{
// ... 之前的代码
reticle.position = worldMousePosition;
}
在 Unity 编辑器中,将 Reticle 物体拖拽到 PlayerController 组件的 reticle 槽位上。
2.3 解决准心旋转问题
运行游戏后会发现一个问题:准心在跟随鼠标移动的同时,自身也在不断旋转。
问题分析:
- Reticle 作为 AimPivot 的子物体,会随父物体一起旋转**(公转)**
- 虽然在局部坐标系中,Reticle 的坐标系没有变化;但在世界坐标系中,Reticle 的坐标系一直在旋转
解决方案:
在更新位置代码后添加一行,将 Reticle 的局部旋转锁定为 0:
void Update()
{
// ... (之前的代码)
reticle.position = worldMousePosition;
reticle.localRotation = Quaternion.identity; // 重置局部旋转
}
虽然现在 Reticle 的位置和旋转都独立于 AimPivot,但我们仍然保留了 AimPivot 的角度计算逻辑。这个角度数据在后续实现角色抬手、武器朝向等功能时仍然有用。
3. 完善子弹发射与目标销毁
3.1 实现子弹发射
在 PlayerController.cs 中添加子弹发射逻辑:
public GameObject projectilePrefab; // 子弹预制体
void Update()
{
// ... (移动和瞄准代码)
// --- 射击逻辑 ---
if (Input.GetMouseButtonDown(0)) // 0 = 左键, 1 = 右键, 2 = 中键
{
GameObject newProjectile = Instantiate(projectilePrefab, transform.position, aimPivot.rotation);
}
}
在编辑器中将 Projectile Prefab 拖拽到 projectilePrefab 槽位上。
GetMouseButtonDown(0): 只在按键按下的瞬间触发一次,适合单发射击GetMouseButton(0): 持续按住时每帧都触发,适合连发射击
3.2 编写子弹移动脚本
创建新脚本 Projectile.cs 并挂载到 Projectile Prefab 上:
using UnityEngine;
public class Projectile : MonoBehaviour
{
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
rb.velocity = transform.right * 10f; // 沿着物体的局部右方向移动
}
private void OnCollisionEnter2D(Collision2D collision)
{
// 暂时设定为:碰到任何物体就销毁自己
Destroy(gameObject);
}
}
transform.right: 物体的局部坐标系右方向,会随物体旋转而改变Vector3.right: 世界坐标系的右方向,始终指向 (1, 0, 0)
3.3 编写目标销毁脚本
创建新脚本 Target.cs 并挂载到 Target Prefab 上:
using UnityEngine;
public class Target : MonoBehaviour
{
private void OnCollisionEnter2D(Collision2D collision)
{
// 检测是否被子弹击中
if (collision.gameObject.GetComponent<Projectile>() != null)
{
Destroy(gameObject); // 销毁目标
}
}
}